home *** CD-ROM | disk | FTP | other *** search
/ Internet Info 1994 March / Internet Info CD-ROM (Walnut Creek) (March 1994).iso / networking / mail / uucp / uubatch.105 / uudebat.c < prev   
Encoding:
C/C++ Source or Header  |  1993-06-08  |  10.6 KB  |  383 lines

  1. /*
  2.  * uudebat.c, uubatch version 1.0.5
  3.  *
  4.  * uudebat: batched UUCP de-batcher
  5.  *
  6.  * Authors :
  7.  *         Baruch Cochavy     (blue@dduster.hellnet.org)
  8.  *         Gil Tene         (devil@hellnet.org).
  9.  *
  10.  *
  11.  * This program will de-batch an incoming batch. Input file is fed via
  12.  * stdin, and is composed of many UUCP files, with and added single 
  13.  * character ('#') at the beginning of each line, each file data section
  14.  * is preceded by the destination filename.
  15.  *
  16.  * This is where security comes in mind. This is one of the main reasons
  17.  * the program is simple - it shouldn't take more then a couple of min. to 
  18.  * figure out what the risks running it are. We have tried to cover all
  19.  * possible security risks : Files are only opened in the uucp machine's
  20.  * spool directory. The program checks to see that the destination
  21.  * file name contains no "/" characters, and that the file is a valid
  22.  * uucp file (only X. and D. files are allowed, no C. files are). 
  23.  *
  24.  * One may find this to be inadequate, and add some more security, sanity,
  25.  * disk space checking and probably another zilions of checks. Fell free
  26.  * to modify it in any way appropriate, but keep in mind your incoming
  27.  * batched mail will be passing here, so be extra careful ro check it before
  28.  * being installed.
  29.  * 
  30.  * Batch File format:
  31.  * ------------------
  32.  *                          file name #1
  33.  *                          # line 1 of file 1
  34.  *                          # line 2 of file 1
  35.  *                          #      .
  36.  *                          #      .
  37.  *                          #      .
  38.  *                          file name #2
  39.  *                          # line 1 of file 2
  40.  *                          # line 2 of file 2
  41.  *                          #      .
  42.  *                          #      .
  43.  *                          #      .
  44.  *
  45.  * In version 1.0.4 and later we have added an optional "escape" line
  46.  * syntax. The purpose of these lines is to allow more data about the
  47.  * batched files to be added in later releases for "smarter" operation
  48.  * (such as data for e-mail non-delivery bounces, etc.).
  49.  * "escape" lines are lines in X. files which start with a "#!UUBATCH :" 
  50.  * (in addition to the leading # char.). These lines, if encountered, 
  51.  * are filtered out in uudebat 1.0.4 or later. The lines will not be 
  52.  * filtered out in earlier versions, but they would also do no harm, 
  53.  * since they are in uucp X. file comment format, and will be totaly 
  54.  * ignored by uuxqt. Escape line length may NOT exceed 253 characters.
  55.  *
  56.  * We have also defined the future syntax of uubatch escape lines :
  57.  *
  58.  * #!UUBATCH : {uubatch facility} { parameter1 } { parameter2 } ...
  59.  * 
  60.  * Where uubatch facility is something like "DATE" (for e-mail date)
  61.  * and each parameter is an ASCII string.
  62.  *
  63.  * All uubatch facility names NOT beginning with X- (e.g. X-FOO) are 
  64.  * reserved by us for future use. If people wish to add their own 
  65.  * extentions and features using the escape line feature, they should
  66.  * use facility names beginning with X-.
  67.  * 
  68.  *
  69.  */
  70.  
  71. #include <stdio.h>
  72. #include <string.h>
  73. #ifdef sun386i
  74. extern char *getenv();
  75. #else
  76. #include <stdlib.h>
  77. #endif
  78.  
  79. #ifdef SCO
  80. #include <limits.h>
  81. #define PMAX _POSIX_PATH_MAX
  82. #define index(_a,_b) strchr(_a,_b)
  83. #else
  84. #include <sys/param.h>
  85. #ifdef HAS_STRING
  86. #include <string.h>
  87. #else
  88. #include <strings.h>
  89. #endif
  90. #define PMAX MAXPATHLEN
  91. #endif
  92.  
  93. #include "patchlevel.h"
  94.  
  95. /* NOTICE: COMMAND is defined as the system executed string to result in
  96.  * error message being mailed. You may like to change that.
  97.  *
  98.  * This string is used in a sprintf statment, and if changed, should 
  99.  * include provisions for one string which represents the file name 
  100.  * holding the error text.
  101.  */
  102.  
  103. #define COMMAND "mail -s uudebat_error_report postmaster < %s\n"
  104.  
  105. /* UUBATCH escape perfix. Lines in the batch that begin with this prefix
  106.  * won't get written to any output file. This line will be used in future
  107.  * uubatch version to support various enhancments
  108.  */
  109.  
  110. #define ESCAPE_PREFIX  "#!UUBATCH :"
  111. #define ESCAPE_PREFIX2 "#%UUBATCH :"
  112.  
  113. #define EXIT_ERROR 1
  114. #define EXIT_NO_ERROR 0
  115.  
  116. /* define line_state constants */
  117.  
  118. #define BEGINING_OF_LINE 0
  119. #define MIDDLE_OF_LINE   1
  120. #define ESCAPE_LINE   2
  121.  
  122. #define str_eq(_s1,_s2) (!strncmp(_s1,_s2,strlen(_s2)))
  123.  
  124. /* NOTE: the value of LINE_LENGTH below should never be set to anything 
  125.  * less then a UUBATCH escape length, which is 12, to avoid spliting
  126.  * the escape sequence amond two lines. Escape lines, however, can
  127.  * bo of any liength.
  128.  */
  129. #define LINE_LENGTH 8192
  130.  
  131. static void notify_error();
  132. static FILE * secure_fopen();
  133.  
  134. int main()
  135. {
  136.     int
  137.         line_state,        /* state of input line being read: */
  138.  
  139.         error_flag=0,    /* error flag for the file being   */
  140.                         /* created.                        */
  141.  
  142.         system_error=0;    /* global error flag               */
  143.  
  144.     char
  145.         filename[PMAX],
  146.         output_filename[PMAX],
  147.         pathname[PMAX],
  148.         line[LINE_LENGTH],
  149.         *t,
  150.         *orig_system;
  151.  
  152.     FILE
  153.         *fo;        /* output stream file pointer */
  154.  
  155.  
  156.     /* identify the machine this batch will come into : */
  157.  
  158.     if (!(orig_system = getenv("UU_MACHINE")))
  159.     {
  160.         fprintf(stderr,"uudebat: must know originating mach. name\n");
  161.         exit(255);
  162.     }
  163.  
  164.     /* cd to uucp machine's spool directory : */
  165.  
  166.     sprintf(pathname,"%s/%s",UUCP_SPOOL_DIR,orig_system);
  167.     if (chdir(pathname))
  168.     { 
  169.         perror("Cannot cd to node's spool directory"); 
  170.         exit(254); 
  171.     }
  172.  
  173.     /* read the first line in the input stream. This line should  */
  174.     /* contain a file name :                                      */
  175.  
  176.     t = fgets(filename, PMAX, stdin);
  177.  
  178.     /* Loop until there are no more lines in stdin :              */
  179.  
  180.     while (t)
  181.     {    
  182.         error_flag = 0;
  183.  
  184.         /* line_state is used to control the hadling of lines as
  185.          * they are read. UUdebat assumes no limit to line length. 
  186.          * And therefor needs the line_state variable to distinguish
  187.          * the following states :
  188.          * 
  189.          * BEGINING_OF_LINE - When at the begining of a new line.
  190.          * MIDDLE_OF_LINE   - When in the middle of a line. Indicates it is
  191.          *                    not needed to check for UUDEBAT line headers
  192.          *                    or UUDEBAT escape line.
  193.          * ESCAPE_LINE      - When tossing escape line to the wind.
  194.          */
  195.  
  196.         line_state = BEGINING_OF_LINE;
  197.  
  198.         /* Open the output file based on the requested file name,    */
  199.         /* while making sure security is not broken :                */
  200.  
  201.         fo = secure_fopen(filename,output_filename,&error_flag);
  202.  
  203.         /* Each input batch line is read, until that file is at end.
  204.          * Each line is stripped of its first character, used to
  205.          * identify it as belonging to that file, and special 
  206.          * "escape" comment lines which may have been inserted in
  207.          * X. files are filtered out :
  208.          */
  209.  
  210.         while (t=fgets(line, LINE_LENGTH, stdin)) 
  211.         {
  212.             if (line_state == BEGINING_OF_LINE )  /* first part of line */
  213.             {
  214.                 if ( *t != '#' ) break;
  215.                 t++;
  216.             }
  217.  
  218.             if ( line_state == BEGINING_OF_LINE
  219.               && ( str_eq(t, ESCAPE_PREFIX ) || str_eq(t, ESCAPE_PREFIX2) )
  220.                )
  221.                 line_state = ESCAPE_LINE;
  222.  
  223.             /* if rest of line, or line does not begin with  */
  224.             /* UUBATCH escape, emit it.                      */
  225.  
  226.             if ( (line_state != ESCAPE_LINE) || (filename[0]!='X') )
  227.                 fputs(t,fo);
  228.  
  229.             if ( line_state == BEGINING_OF_LINE  )
  230.                                  line_state = MIDDLE_OF_LINE;
  231.             if ( index(t,'\n') ) line_state = BEGINING_OF_LINE;
  232.         }
  233.  
  234.         /* notify of possible error: */
  235.  
  236.          if (ferror(fo))
  237.         {
  238.             sprintf(line,"Write error(s) on \"%s\"", output_filename);
  239.             perror(line);
  240.             break;
  241.         }
  242.  
  243.         /* since there are no more consecutive lines containing '#'
  244.          * as their first character, this file has ended. We
  245.          * therefore close it and start a new one, by moving the 
  246.          * line read into the file name to be opened.
  247.          */
  248.  
  249.         if (fclose(fo))     /* close file, notify of possible error: */
  250.         {
  251.             sprintf(line,"Close error on \"%s\"",output_filename);
  252.             perror(line);
  253.             break;
  254.         }
  255.  
  256.         if (t)    
  257.         {
  258.             if (strlen(line) > PMAX)
  259.             {
  260.             fprintf(stderr,"Filename too long. Truncated.\n");
  261.             fprintf(stderr,"  Original filename: %s\n",line);
  262.             }
  263.             strncpy(filename,line,PMAX);
  264.             filename[PMAX] = '\0';
  265.         }
  266.  
  267.         /* now that the output file is closed, let's see what can we
  268.          * do with it. If it's creation or writing met with some 
  269.          * error, this is the time to tell somebody about it :
  270.          */
  271.  
  272.         if (error_flag) 
  273.             notify_error(output_filename);
  274.  
  275.         /* make sure the exit status reflects an error if one 
  276.          * occurs :
  277.          */
  278.         system_error = ( error_flag ? EXIT_ERROR : EXIT_NO_ERROR );
  279.     }
  280.  
  281.     exit(system_error);
  282. }
  283.  
  284. /* notify_error : notify someone of an error, sending the data file... :
  285.  */
  286. static void
  287. notify_error(output_filename)
  288. char *output_filename;
  289. {
  290.     int i;
  291.     char line[256];
  292.     sprintf(line, COMMAND, output_filename);
  293.     if (i = system(line))
  294.         fprintf(stderr, "uudebat: system error %d\n",i);
  295.     unlink(output_filename);
  296. }
  297.  
  298. /* secure_fopen : Open the output file based on the requested file name,
  299.  * while making sure security is not broken :
  300.  */
  301. static FILE *
  302. secure_fopen(req_filename,output_filename,error_flag)
  303. char *req_filename,*output_filename;
  304. int *error_flag;
  305. {
  306.     FILE
  307.         *fo;
  308.     char
  309.         line[128],
  310.         *t;
  311.     
  312.     if ( t=index( req_filename, '\n' ) ) *t='\0';
  313.  
  314.     /* make sure file name begins with "D." or "X." : */
  315.  
  316.     if ((req_filename[0] != 'D' && req_filename[0] != 'X')
  317.       || req_filename[1] != '.')
  318.     {
  319.         fprintf(stderr,"Can only write D. and X. files: %s\n",
  320.             req_filename);
  321.         *error_flag = 4;
  322.     }
  323.  
  324.     /* Check to verify no '/' is embedded. This will make sure the 
  325.      * file is written in the spool directory, and not anywhere 
  326.      * else ...
  327.      */
  328.  
  329.     if ( index(req_filename, '/') ) 
  330.     {
  331.         fprintf(stderr,"Cannot write to another directory: %s\n",
  332.             req_filename);
  333.         *error_flag = 5;
  334.     }
  335.  
  336.     /* form the output filename : either it is the requested filename
  337.      * (remember that we ARE already cd'ed to the system's uucp spool
  338.      * directory), or we get a temporary filename if the requested
  339.      * one is not legal :
  340.      */
  341.  
  342.     if (*error_flag)
  343.         tmpnam(output_filename);
  344.     else
  345.         strcpy(output_filename,req_filename);
  346.  
  347.     /* We now open the output file : */
  348.  
  349.     if (!(fo = fopen(output_filename,"w")))
  350.     {
  351.         sprintf(line,"Cannot open output file \"%s\"", output_filename);
  352.         perror(line);
  353.         
  354.         /* If file was not opened, open a temporary one : */
  355.  
  356.         tmpnam(output_filename);
  357.  
  358.         *error_flag = 6;
  359.  
  360.         if (!(fo = fopen(output_filename,"w")))
  361.         {
  362.             fprintf(stderr,"Second open error in a row ... \n");
  363.             exit(255);
  364.         }
  365.     }
  366.  
  367.     /* if there was an error, the file we are writing to will eventually
  368.      * end up as mail to POSTMASTER, so we now put a header on it :
  369.      */
  370.     
  371.     if (*error_flag)
  372.     {
  373.         fprintf(fo, "\nuudebat: error (%d)\n",*error_flag);
  374.         fprintf(fo, "Original file name requested was \"%s\"\n",
  375.             req_filename);
  376.         fprintf(fo, "\nMail body enclosed.\n");
  377.         fprintf(fo, "======================================\n");
  378.     }
  379.  
  380.     return fo;
  381. }
  382.  
  383.